Spring Framework手动装配

Spring Framework手动装配

Spring Boot 的自动装配源于Spring Framework的手动装配。

1. Spring模式注解装配

定义:一种用于声明在应用中扮演”组件”角色的注解

举例

Spring框架中常见的@Component@Service@Configuration等等。

装配方式:

  1. 配置文件xml中配置<context:component-scan>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/springcontext.xsd">
    <!-- 激活注解驱动特性 -->
    <context:annotation-config />
    <!-- 找寻被 @Component 或者其派生 Annotation 标记的类(Class),将它们注册为 Spring Bean -->
    <context:component-scan base-package="com.test.spring.boot" />
    </beans>
  2. @ComponentScan

1
2
3

@ComponentScan(basePackages = "com.test.spring.boot")
public class SpringConfiguration { ... }

自定义模式注解

前往SPRING INITIALIZR 生成一个工程。Snipaste_2019-01-05_15-37-55点击Generate Project等待下载完成后解压,使用IDEA导入工程,选MAVEN工程一直下一步即可。
导入后等待MAVEN下载好依赖,完成后截图如下。Snipaste_2019-01-05_15-47-37
下面开始自定义一个注解,新建一个包annotation,在这个包里新建一个注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.myjdemo.springbootlearning.annotation;

import org.springframework.stereotype.Repository;
import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repository
public @interface FirstLevelRepository {

String value() default "";

}

按住ctrl点击@Repository可以看到Repository注解的代码
Snipaste_2019-01-05_15-59-15
继续点进Repository中的@Component
Snipaste_2019-01-05_15-59-29

根据Spring官方的一句话

凡是被@Component元标注的注解,如@Service,当任何组件标注它时,也被视作组件扫描的候选对象。

在这边@Repository标注了@Component注解,也就是说当一个组件标注了@Repository注解时,也会被视作@Component扫描到,这就是模式注解的派生性。这里我们注意,这些注解中的String value() default "";也保持一致。
我们自定义的@FirstLevelRepository上面标注了@Repository@Repository上标注了@Component,因此,当一个组件标注了我们自定义的@FirstLevelRepository注解时,也会被扫描到。
下面我们来编写代码验证一下。先建一个repository包,里面存放我们测试的Bean,在这个包里建一个测试Bean并且使用我们自定义的注解。

1
2
3
4
5
6
package cn.myjdemo.springbootlearning.repository;

import cn.myjdemo.springbootlearning.annotation.FirstLevelRepository;

@FirstLevelRepository(value = "myFirstLevelRepository") // Bean名称
public class MyFirstLevelRepository {}

在建一个bootstrap包,里面建一个RepositoryBootstrap引导类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.myjdemo.springbootlearning.bootstrap;

import cn.myjdemo.springbootlearning.repository.MyFirstLevelRepository;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan(basePackages = "cn.myjdemo.springbootlearning.repository")
public class RepositoryBootstrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(RepositoryBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
//myFirstLevelRepository是否存在
MyFirstLevelRepository repository = context.getBean("myFirstLevelRepository",MyFirstLevelRepository.class);
System.out.println(repository);
//关闭上下文
context.close();
}
}

运行main方法,发现已经扫描到我们注解的类并且打印出来了。Snipaste_2019-01-05_16-30-14
而另一个特性层次性就是说我们自定义的这个注解不仅能标注到组件上面去,也可以标注到另一个注解上面。
我们在annotation中再新建一个注解@SecondLevelRepository

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.myjdemo.springbootlearning.annotation;

import java.lang.annotation.*;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@FirstLevelRepository
public @interface SecondLevelRepository {

String value() default "";

}

然后将repository的注解改为SecondLevelRepository

1
2
3
4
5
6
package cn.myjdemo.springbootlearning.repository;

import cn.myjdemo.springbootlearning.annotation.SecondLevelRepository;

@SecondLevelRepository(value = "myFirstLevelRepository") //Bean名称
public class MyFirstLevelRepository {}

然后直接运行之前的main方法,仍然能扫描到。

2.Spring @Enable模块装配

定义:具备相同领域的功能组件集合,组合所形成的一个独立的单元。

举例

@EnableWebMvc@EnableAutoConfiguration

实现方式

  1. 注解驱动方式
1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
1
2
3
4
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport{
...
}
  1. 接口编程方式
1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
/**
* {@inheritDoc}
* @return {@link ProxyCachingConfiguration} or {@code
AspectJCacheConfiguration} for
* {@code PROXY} and {@code ASPECTJ} values of {@link
EnableCaching#mode()}, respectively
*/
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[]{ AutoProxyRegistrar.class.getName(),ProxyCachingConfiguration.class.getName() };
case ASPECTJ:
return new String[] { AnnotationConfigUtils.CACHE_ASPECT_CONFIGURATION_CLASS_NAME };
default:
return null;
}
}

自定义@Enable模块

  1. 基于注解@EnableHelloWorld

annotaion包中新建一个注解EnableHelloWorld

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.myjdemo.springbootlearning.annotation;

import cn.myjdemo.springbootlearning.configuration.HelloWorldConfiguration;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorld {
}

然后新建一个configuration包,然后在里面新建一个HelloWorldConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.myjdemo.springbootlearning.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HelloWorldConfiguration {

@Bean
public String helloWorld(){
return "Hello World";
}
}

最后在bootstrap包中新建一个EnableHelloWorldBootstrap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.myjdemo.springbootlearning.bootstrap;

import cn.myjdemo.springbootlearning.annotation.EnableHelloWorld;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

@EnableHelloWorld
public class EnableHelloWorldBootstrap {

public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
//helloWorld是否存在
String helloWorld = context.getBean("helloWorld",String.class);

System.out.println(helloWorld);

//关闭上下文
context.close();
}
}

运行main函数,能发现控制台正常输出的Hello World

  1. 基于接口编程方式

先在annotation包中新建一个类

1
2
3
4
5
6
7
8
9
10
11
12
package cn.myjdemo.springbootlearning.annotation;

import cn.myjdemo.springbootlearning.configuration.HelloWorldConfiguration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class HelloWorldImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{HelloWorldConfiguration.class.getName()};
}
}

然后将之前的EnableHelloWorld注解中@Import(HelloWorldConfiguration.class)注释掉,修改为@Import(HelloWorldImportSelector.class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.myjdemo.springbootlearning.annotation;

import cn.myjdemo.springbootlearning.configuration.HelloWorldConfiguration;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//@Import(HelloWorldConfiguration.class)
@Import(HelloWorldImportSelector.class)
public @interface EnableHelloWorld {
}

运行EnableHelloWorldBootstrap,也能正常打印出Hello World

基于接口编程的方式我们可以在Selector中制定条件语句,能有多种返回值(见举例中的CachingConfigurationSelector),是弹性的,而基于注解的方式就不可以。

3.Spring条件装配

定义:Bean装配的前置判断

举例

@Profile@Conditional

实现方式

  1. 配置方式@Profile
  2. 编程方式@Conditional
1
2
3
4
5
6
7
8
9
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
Class<?>[] value() default {};

String[] name() default {};
}

自定义条件装配

  1. 基于配置方式实现 @Profile

下面模拟一个计算服务,多整数求和sum,@Profile("java7")是假设在java7版本下的求和运算,使用for循环实现;@Profile("java8")是假设在java8版本下的求和运算,使用lambda表达式实现。

先新建一个service包,然后在里面写一个计算接口CalculateService

1
2
3
4
5
6
7
8
9
10
package cn.myjdemo.springbootlearning.service;

public interface CalculateService {
/**
* 给一系列整数求和
* @param values 待求和整数
* @return
*/
Integer sum(Integer... values);
}

然后在这个包下面分别写两个实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package cn.myjdemo.springbootlearning.service;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
@Profile("java7")
@Service
public class Java7CalculateService implements CalculateService {
@Override
public Integer sum(Integer... values) {
System.out.println("java7 for循环实现");
int sum = 0;
for(int i=0;i<values.length;i++){
sum += values[i];
}
return sum;
}


public static void main(String[] args) {
CalculateService calculateService = new Java7CalculateService();
System.out.println(calculateService.sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.myjdemo.springbootlearning.service;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import java.util.stream.Stream;

@Profile("java8")
@Service
public class Java8CalculateService implements CalculateService {
@Override
public Integer sum(Integer... values) {
System.out.println("java8 lambda实现");
return Stream.of(values).reduce(0,Integer::sum);
}


public static void main(String[] args) {
CalculateService calculateService = new Java8CalculateService();
System.out.println(calculateService.sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
}

}

然后在bootstrap包中新建一个CalculateServiceBootstrap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package cn.myjdemo.springbootlearning.bootstrap;

import cn.myjdemo.springbootlearning.service.CalculateService;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication(scanBasePackages = "cn.myjdemo.springbootlearning.service")
public class CalculateServiceBootstrap {

public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(CalculateServiceBootstrap.class)
.web(WebApplicationType.NONE)
.profiles("java7")
.run(args);
//CalculateService是否存在
CalculateService calculateService = context.getBean(CalculateService.class);

System.out.println(calculateService.sum(1,2,3,4,5,6,7,8,9,10));

//关闭上下文
context.close();
}
}

由于配置了java7,因此会使用java7的实现类,将.profiles("java7")改成.profiles("java8")之后,就会改用java8的实现类。

  1. 基于编程方式实现 @ConditionalOnSystemProperty

先新建一个包condition,新建一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package cn.myjdemo.springbootlearning.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Map;

/**
* 系统属性条件判断
*/
public class OnSystemPropertyConditional implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Map<String,Object> attributes = annotatedTypeMetadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());

String propertyName = String.valueOf(attributes.get("name"));
String propertyValue = String.valueOf(attributes.get("value"));

String javaPropertyValue = System.getProperty(propertyName);

return propertyValue.equals(javaPropertyValue);
}
}

再新建一个注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package cn.myjdemo.springbootlearning.condition;

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.*;

/**
* Java 系统属性 条件判断
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Conditional({OnSystemPropertyConditional.class})
public @interface ConditionalOnSystemProperty {

/**
* Java系统属性的名称
* @return
*/
String name();

/**
* Java 系统属性值
* @return
*/
String value();
}

再在bootstrap包中新建一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package cn.myjdemo.springbootlearning.bootstrap;

import cn.myjdemo.springbootlearning.condition.ConditionalOnSystemProperty;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;


public class ConditionalOnSystemPropertyBootstrap {

@Bean
@ConditionalOnSystemProperty(name = "user.name",value = "Administrator")
public String helloWorld(){
return "Hello World";
}

public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(ConditionalOnSystemPropertyBootstrap.class)
.web(WebApplicationType.NONE)
.run(args);
//helloWorld是否存在
String helloWorld = context.getBean("helloWorld",String.class);

System.out.println(helloWorld);

//关闭上下文
context.close();
}

}

运行后能正常输出Hello World。当把ConditionalOnSystemPropertyBootstrap中helloWorld()方法上的注解的value改为非系统属性中user.name的值时,就会报扫描不到bean的错,因为我们在OnSystemPropertyConditional中编写了只有当从系统取出来的name对应的value值与我们注解里name对应的value值一致时才能被扫描到。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×